package tazadum.razzmatazz.scene;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;

import tazadum.engine.Engine;
import tazadum.engine.event.SyncEventQueue;
import tazadum.engine.math.Vector;
import tazadum.engine.math.Vectors;
import tazadum.engine.render.Renderable;
import tazadum.engine.scene.CameraScene;
import tazadum.engine.util.HeightMap;
import tazadum.razzmatazz.component.SplineWormStrip;
import tazadum.util.BufferUtil;

public class WireframeLandscapeScene extends CameraScene {
	private static int WIDTH = 60;
	private static int HEIGHT = 60;

	private IntBuffer indices;
	private FloatBuffer vertices;
	private FloatBuffer colors;

	private float p_cameraX;
	private float p_cameraY;
	private float p_cameraZ;

	private final int polygons = (WIDTH - 1) * (HEIGHT - 1) * 2;

	protected final float[] ambient = { 0.8f, 0.8f, 0.8f, 1.0f };
	protected final float[] diffuse = { 1.0f, 1.0f, 1.0f, 1.0f };
	protected final float[] specular = { 1.0f, 1.0f, 1.0f, 1.0f };
	protected final float[] light1_pos = { 0.0f, 10.0f, 0.0f, 1.0f };

	protected float temp3[] = new float[3];
	protected float temp4[] = new float[4];

	protected HeightMap heightMap;
	private final List<Renderable> renderables = new LinkedList<Renderable>();

	public WireframeLandscapeScene() {
	}

	@Override
	public void loadResources(final Engine engine, final SyncEventQueue queue) {
		heightMap = new HeightMap(new Vector(0, 0, 0), 0.1f, 10f);
		colors = BufferUtil.newFloatBuffer(3 * WIDTH * HEIGHT);
		colors.rewind();

		vertices = BufferUtil.newFloatBuffer(3 * WIDTH * HEIGHT);
		vertices.rewind();

		for (int z = 0; z < HEIGHT; z++) {
			for (int x = 0; x < WIDTH; x++) {
				final float xpos = x;
				final float zpos = z;

				vertices.put(xpos);
				vertices.put(0);
				vertices.put(zpos);

				colors.put(1);
				colors.put(1);
				colors.put(1);
			}
		}
		shift(0);
		vertices.rewind();
		colors.rewind();

		indices = BufferUtil.newIntBuffer(3 * polygons);
		indices.rewind();
		for (int z = 0; z < HEIGHT - 1; z++) {
			for (int x = 0; x < WIDTH - 1; x++) {
				final int index = x + z * WIDTH;
				indices.put(index);
				indices.put(index + 1);
				indices.put(index + 1 + WIDTH);

				indices.put(index);
				indices.put(index + 1 + WIDTH);
				indices.put(index + WIDTH);

				calcColor(index, index + 1, index + 1 + WIDTH);
				calcColor(index, index + 1 + WIDTH, index + WIDTH);
			}
		}
		indices.rewind();
		colors.rewind();
	}

	private void calcColor(final int a, final int b, final int c) {
		final Vector A = new Vector(vertices.get(a * 3 + 0), vertices.get(a * 3 + 1), vertices.get(a * 3 + 2));
		final Vector B = new Vector(vertices.get(b * 3 + 0), vertices.get(b * 3 + 1), vertices.get(b * 3 + 2));
		final Vector C = new Vector(vertices.get(c * 3 + 0), vertices.get(c * 3 + 1), vertices.get(c * 3 + 2));

		final Vector AB = Vectors.sub(B, A);
		final Vector AC = Vectors.sub(C, A);
		final Vector normal = Vectors.cross(AB, AC).normalize();

		final float color = 1f - ((normal.x < 0) ? 0 : normal.x * normal.x * 0.75f) - (1f - heightMap.raw(A.x, A.z))
				* 0.05f;

		for (int i = 0; i < 3; i++) {
			colors.put(a * 3 + i, color);
			colors.put(b * 3 + i, color);
			colors.put(c * 3 + i, color);
		}
	}

	@Override
	public void init(final Engine engine, final GLAutoDrawable drawable, final SyncEventQueue queue) {
		final GL2 gl = drawable.getGL().getGL2();
		final Random random = engine.getRandom();

		gl.glEnable(GL.GL_LINE_SMOOTH);
		gl.glHint(GL.GL_LINE_SMOOTH_HINT, GL.GL_NICEST);
		// gl.glEnable(GL.GL_BLEND);
		// gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
		gl.glShadeModel(GL2.GL_FLAT);

		setupLight1(gl);

		p_cameraX = WIDTH / 2 + 5;
		p_cameraY = 12;
		p_cameraZ = HEIGHT / 2 + 5;
		engine.getParameterManager().register("Camera X", "p_cameraX", this);
		engine.getParameterManager().register("Camera Y", "p_cameraY", this);
		engine.getParameterManager().register("Camera Z", "p_cameraZ", this);

		for (int i = 5; i < WIDTH - 5; i += 2) {
			final Vector from = new Vector(i, 0, 0);
			final Vector to = new Vector(i, 0, HEIGHT);
			renderables.add(new SplineWormStrip(gl, from, to, heightMap, random));
		}
	}

	@Override
	public void display(final GLAutoDrawable drawable, final SyncEventQueue queue, final float time) {
		final GL2 gl = drawable.getGL().getGL2();

		eye.x = p_cameraX + (float) Math.cos(time * 0.1f) * 15;
		eye.y = p_cameraY;
		eye.z = p_cameraZ + (float) Math.sin(time * 0.1f) * 15;

		target.x = WIDTH / 2;
		target.y = 2;
		target.z = HEIGHT / 2;

		setupCamera(gl);
		heightMap.shift(time * 0.1f);
		shift(time);

		// render

		gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
		gl.glVertexPointer(3, GL2.GL_FLOAT, 0, vertices);
		gl.glEnableClientState(GL2.GL_COLOR_ARRAY);
		gl.glColorPointer(3, GL.GL_FLOAT, 0, colors);

		gl.glColor3f(1f, 1f, 1f);
		gl.glPolygonMode(GL.GL_BACK, GL2.GL_FILL);
		gl.glDrawElements(GL.GL_TRIANGLES, polygons * 3, GL.GL_UNSIGNED_INT, indices);

		gl.glDisableClientState(GL2.GL_COLOR_ARRAY);
		gl.glLineWidth(1);
		gl.glColor3f(.6f, .6f, .6f);
		gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2.GL_LINE);
		gl.glDrawElements(GL.GL_TRIANGLES, polygons * 3, GL.GL_UNSIGNED_INT, indices);

		gl.glPolygonMode(GL.GL_BACK, GL2.GL_FILL);
		gl.glDisableClientState(GL2.GL_COLOR_ARRAY);
		gl.glColor3f(1f, 0f, 0f);
		for (final Renderable obj : renderables) {
			obj.render(gl, time);
		}
	}

	private void shift(final float time) {
		for (int z = 0; z < HEIGHT; z++) {
			for (int x = 0; x < WIDTH; x++) {
				final float ypos = heightMap.at(x, z);
				final int index = x + z * WIDTH;
				vertices.put(index * 3 + 1, ypos);
			}
		}
		vertices.rewind();
	}

	private void setupLight1(final GL2 gl) {
		gl.glEnable(GL2.GL_LIGHT1);
		gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_AMBIENT, ambient, 0);
		gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_DIFFUSE, diffuse, 0);
		gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_DIFFUSE, specular, 0);
		gl.glLightf(GL2.GL_LIGHT1, GL2.GL_CONSTANT_ATTENUATION, 0.2f);
		gl.glLightfv(GL2.GL_LIGHT1, GL2.GL_POSITION, light1_pos, 0);
	}
}
